/*
 * Copyright (c) Huawei Technologies Co., Ltd. 2020-2022.
 * Description: support flash kbox feature
 * Author: lizhe <lizhe67@huawei.com>
 * Create: 2020-12-01
 */

#include <linux/kbox/kbox.h>
#include <asm/uaccess.h>
#include <linux/proc_fs.h>
#include <linux/fs.h>
#include <linux/seq_file.h>
#include <linux/slab.h>
#include <linux/mtdkbox.h>

static struct proc_dir_entry *flash_proc_dir;
static struct proc_dir_entry *proc_flash;

static int flash_region_id;
static unsigned int flash_region_size;

static int flash_kbox_register_init(unsigned int size)
{
	int tmpid;
	struct kbox_region flash_region;

	if (size > MAX_LOG_SIZE)
		return -EINVAL;

	strncpy(flash_region.name, "flash", KBOX_REGION_NAME_LEN);
	flash_region.name[KBOX_REGION_NAME_LEN - 1] = '\0';
	flash_region.size = size * 1024;

	tmpid = kbox_register_region(flash_region);
	if (tmpid < 0) {
		printk(KERN_WARNING "flash register region failed\n");
		return tmpid;
	}

	flash_region_id = tmpid;
	flash_region_size = size;

	return 0;
}

int get_flash_kbox_id(void)
{
	return flash_region_id;
}
EXPORT_SYMBOL_GPL(get_flash_kbox_id);

static int flash_proc_show(struct seq_file *m, void *v)
{
	seq_printf(m, "%u\n", flash_region_size);
	return 0;
}

static int flash_open_proc(struct inode *inode, struct file *file)
{
	return single_open(file, flash_proc_show, NULL);
}

/* the max u32(4294967295) length + 1 */
#define MAX_FLASH_SIZE_LENGTH 11

static ssize_t flash_write_proc(struct file *file, const char __user *buf,
			    size_t count, loff_t *offs)
{
	unsigned long val;
	int ret;

	if (!capable(CAP_SYS_ADMIN))
		return -EPERM;

	if (count > MAX_FLASH_SIZE_LENGTH - 1)
		return -EINVAL;

	ret = kstrtoul_from_user(buf, count, 0, &val);
	if (ret < 0)
		return ret;

	if (val == 0)
		return -EINVAL;

	/* creat region */
	ret = flash_kbox_register_init((unsigned int)val);
	if (ret < 0)
		return ret;

	return count;
}

static const struct proc_ops flash_proc = {
	.proc_open		= flash_open_proc,
	.proc_read		= seq_read,
	.proc_lseek		= seq_lseek,
	.proc_write		= flash_write_proc,
	.proc_release	= single_release,
};

void create_mtd_kbox_proc(void)
{
	flash_proc_dir = proc_mkdir("flash", NULL); /* Create flash proc directory */
	if (!flash_proc_dir) {
		/* flash_proc_dir fail not inpact mtd */
		printk(KERN_ERR "create /proc/flash failed\n");
	} else {
		proc_flash = proc_create("log_region_size", (mode_t)0600, flash_proc_dir, &flash_proc);
		if (!proc_flash) {
			printk(KERN_ERR "create /proc/flash/log_region_size failed\n");
			remove_proc_entry("flash", NULL);
			flash_proc_dir = NULL;
		}
	}
}

void destroy_mtd_kbox_proc(void)
{
	if (proc_flash) {
		remove_proc_entry("log_region_size", flash_proc_dir);
		proc_flash = NULL;
	}
	if (flash_proc_dir) {
		remove_proc_entry("flash", NULL);
		flash_proc_dir = NULL;
	}
}
